Chrome 從 84 版開始將 Cookie 的 SameSite 屬性預設為
Lax
,使用到 Third-party cookies 的服務若沒有設定 SameSite 都可能受到影響。
Cookies 是網頁服務中用來儲存狀態的機制,常被用在保持登入、購物車、廣告追蹤等等,但在 Cookies 的廣泛使用下,同時也伴隨著隱私和安全的疑慮,而 SameSite 的出現就是為了解決這些問題。
依據 Cookie 的來源(Set-Cookie
),每個 Cookie 都有專屬的 Domain,以使用者瀏覽器當下的網址來看,只要 Cookie 的 Domain 和目前的網址相符就是 First-Party,反之就是 Third-Party。
例如瀏覽 a.com
網站時發送 Request 到 third-party.com
並拿到了 third-party.com
的 Cookie,由於瀏覽器會在 Request 時自動帶上相同 Domain 的 Cookie,之後瀏覽了其他網站如 b.com
時若也發送 Request 到 third-party.com
,Server 就會收到 Cookie,對這兩個網站來說 third-party.com
的 Cookie 就是 Third-party。
如果瀏覽符合 third-party.com
Domain 的網站也會帶上 Cookie,此時這個 Cookie 就稱為 First-party。
剛才的例子提到以 Domain 是否符合來判定 Cookie 的種類,不過更好的說法應該是以 Site 是否相同來判定,而這和常常看到的 Same-origin 是否有關呢?
Origin 是由 Scheme, Host, Port 組成,判定方式非常簡單,只要兩個網址的 Scheme、Host 和 Port 都相同就是 Same-origin,其餘皆是 Cross-origin。
Same-Site 的判定則牽涉到 Effetive top-level domains(eTLDs),所有的 eTLDs 被定義在 Public Suffix List 中,而 Site 是由 eTLD 加上一個前綴組成。
舉例來說:github.io
存在 Public Suffix List 之中,加上一個前綴(例如 a.github.io
) 就是一個 Site,因此 a.github.io
和 b.github.io
是兩個不同的 Site(Cross-site)。
example.com
不存在 Public Suffix List 之中,但 .com
存在,因此 example.com
是一個 Site,a.example.com
和 b.example.com
就是同一個 Site(Same-site)。
注意 Site 不包含 Port,即使 Port 不同也可以是 Same-site
「任何 Request 都帶上該 Domain 的 Cookie」的機制同時也帶來了安全和其他問題,其中最重要的就是 Cross-site request forgery(CSRF)。
假設使用者曾經登入過 example.com
並取得 Cookie,當使用者瀏覽惡意網站 evil.com
時,網站中的 JavaScript 可以對 example.com/pay?amount=1000
發出 POST Request,瀏覽器會自動帶上 example.com
的 Cookie,使用者就在完全不知情的狀況下付了 1000 元,Server 無法判定這個 Request 是從何而來。
Cookie 本身無法被設定為只在 First-party 環境才發送,因此 Request 在任何環境都會帶上 Cookie,Server 無法辨識 Request 來源只能照常回覆,同時也讓 Client 浪費流量送出無用的 Cookie,
有了 SameSite 屬性後,就可以個別設定 Cookie 在不同環境下的發送條件。
SameSite 屬性共有三種值,設定為 Strict
或 Lax
可以限制 Cookie 只在 Same-Site Request 帶上,若不填則依據瀏覽器可能有不同行為,以 Chrome 來說預設值為 Lax
。
只在 First-party 環境下帶上 Cookie,但這有個問題,假設使用者在 example.com
看到一條 FB 貼文連結(假設為 fb.com
),就算使用者曾經登入過 fb.com
取得了 Cookie,點擊連結後因為兩個網站為 Cross-site,不會帶上 Cookie,只能看到登入頁面。
因此 Strict
適合用在操作,例如刪除貼文、付款等等。
為了解決 Strict
過於嚴格的限制,Lax
在以下情況即使是 Cross-site 依然會送出 Cookie:
<a href="...">
<form method="GET">
<link rel="prerender" href="...">
這幾個情況有兩個共通點:都是 GET 且皆會觸發網頁跳轉(Navigation),如此一來就能避免 Strict
需要重新登入的問題,也不會在瀏覽其他網站時毫不知情的送出 Cookie。
然而為了避免破壞某些現有的登入流程,Chrome 目前在 SameSite=Lax
放寬了一點限制,給開發者更多時間喘息。
在 Cookie 被設定的兩分鐘內,無論 Request Method 是甚麼,只要觸發 Top-level 頁面跳轉都會帶上 Cookie,也就是讓瀏覽器換了頁面,例如送出表單 <form method="POST">
。
詳情請見關於 Lax + POST 的討論串。
想要送出 Third-party cookie 就必須設定為 SameSite=None; Secure
,沒錯,現在起想要在測試環境送出 Third-party cookie 請準備 https://localhost
。
另外以 XHR/Fetch 送出 Cross-Origin Request 需要另外設定 withCredentials: true
才會帶上 Cookie 和讓 Response header 的 Set-Cookie
生效,而 Server 端要在 Response header 中設定 Access-Control-Allow-Credentials: true
,JavaScript 才能存取 Response 的內容。
並不是所有瀏覽器都已經支援最新的 SameSite 規則,因此可以在 Server 加入一些暫時的 Workaround 來支援多種瀏覽器:
此種方式幾乎可以解決所有瀏覽器的問題,缺點就是 Cookie 都會變成兩份:
Set-cookie: name=value; SameSite=None; Secure
Set-cookie: name-legacy=value; Secure
Server 端的程式碼:
if (req.cookies['name']) {
// 有新的就用新的
cookieVal = req.cookies['name'];
} else if (req.cookies['name-legacy']) {
// 不然就用舊的
cookieVal = req.cookies['name-legacy'];
}
以 Request 的 User agent 判斷瀏覽器來決定 Set-Cookie
的內容,這種方式只需要修改設定 Cookie 的程式碼,不用修改 Parse 的部分,但這種判斷方式相對變數較多,比較容易設定成錯誤的 Cookie 。
SameSite=Lax
,Cross-site 環境下無法送出。SameSite=None; Secure
。
Understanding "same-site" and "same-origin"
SameSite cookies explained
SameSite cookie recipes
SameSite sandbox